package com.spring.arch.uaa.oauth2.service;

import com.spring.arch.common.exception.ExceptionHolder;
import com.spring.arch.common.security.UserPrincipal;
import com.spring.arch.uaa.bean.UserClientBean;
import com.spring.arch.uaa.enums.Constant;
import com.spring.arch.uaa.enums.ControlRange;
import com.spring.arch.uaa.errorcode.UaaErrorCode;
import com.spring.arch.uaa.model.*;
import com.spring.arch.uaa.oauth2.handler.PasswordSaltSource;
import com.spring.arch.uaa.repository.TenantRepository;
import com.spring.arch.uaa.repository.UserRepository;
import com.spring.arch.common.exception.ExceptionHolder;
import com.spring.arch.common.security.UserPrincipal;
import com.spring.arch.uaa.bean.UserClientBean;
import com.spring.arch.uaa.enums.Constant;
import com.spring.arch.uaa.enums.ControlRange;
import com.spring.arch.uaa.model.*;
import com.spring.arch.uaa.oauth2.converter.SecurityUserConverter;
import com.spring.arch.uaa.oauth2.converter.UserPrincipalConverter;
import com.spring.arch.uaa.oauth2.handler.PasswordSaltSource;
import com.spring.arch.uaa.repository.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static com.spring.arch.uaa.errorcode.UaaErrorCode.E2001005001;
import static com.spring.arch.uaa.errorcode.UaaErrorCode.E2001005007;

/**
 * 用户认证信息服务
 *
 * @author Frank
 */
@Slf4j
@Service
@Primary
@Transactional(readOnly = true, rollbackFor = Exception.class)
public class OauthUserDetailsServiceImpl implements UserDetailsService, PasswordSaltSource {

    @Autowired
    private UserRepository userRepository;
    @Autowired
    private TenantRepository tenantRepository;
    @Autowired
    private ProjectRepository projectRepository;
    @Autowired
    private AuthorityRepository authorityRepository;
    @Autowired
    private SecurityUserConverter securityUserConverter;
    @Autowired
    private UserPrincipalConverter userPrincipalConverter;
    @Autowired
    private OrganizationRepository organizationRepository;
    @Autowired
    private RoleAuthorityRepository roleAuthorityRepository;
    @Autowired
    private OrganizationEmployeeRepository organizationEmployeeRepository;
    @Autowired
    private OrganizationEmployeeRoleRepository organizationEmployeeRoleRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = loadUserByMobileOrUserName(username);
        return securityUserConverter.toUserPrincipal(user, getAuthorities(user));
    }

    @Override
    public String getSalt(String username) {
        return loadUserByMobileOrUserName(username).getSalt();
    }

    /**
     * 查询用户所属角色
     */
    private Collection<? extends GrantedAuthority> getAuthorities(User user) {
        Collection<Role> authorities = new ArrayList<>();
        List<OrganizationEmployee> employees = organizationEmployeeRepository.findByUserAndStatusTrue(user);
        if (!CollectionUtils.isEmpty(employees)) {
            employees.forEach(e -> {
                List<OrganizationEmployeeRole> employeeRoles =
                        organizationEmployeeRoleRepository.findByOrganizationEmployee(e);
                if (!CollectionUtils.isEmpty(employeeRoles)) {
                    Set<Role> roles =
                            employeeRoles.stream().map(OrganizationEmployeeRole::getRole).collect(Collectors.toSet());
                    authorities.addAll(roles);
                }
            });
        }
        return authorities;
    }

    public User loadUserByMobileOrUserName(String usernameOrMobile) {
        // 首选手机号登陆
        Optional<User> optional = userRepository.findByMobile(usernameOrMobile);
        if (!optional.isPresent()) {
            // 账号登陆
            optional = userRepository.findByAccount(usernameOrMobile);
            if (!optional.isPresent()) {
                throw ExceptionHolder.serviceException(UaaErrorCode.E2001005001, usernameOrMobile);
            }
        }
        return optional.get();
    }

    public UserClientBean loadUserClientBean(String username, UUID clientId) {
        // 获取客户信息
        Tenant tenant = null;
        Project project = null;
        if (!UUID.fromString(Constant.SAAS_MANAGER_CENTER_ID).equals(clientId)) {
            Optional<Tenant> optional = tenantRepository.findById(clientId);
            if (!optional.isPresent()) {
                Optional<Project> projectOptional = projectRepository.findById(clientId);
                if (!projectOptional.isPresent()) {
                    throw ExceptionHolder.serviceException(UaaErrorCode.E2001005007, String.valueOf(clientId));
                } else {
                    project = projectOptional.get();
                    tenant = project.getTenant();
                }
            } else {
                tenant = optional.get();
            }
        }

        // 获取用户信息
        User user = loadUserByMobileOrUserName(username);

        // 加载客户端组织信息
        List<Organization> clientOrgs = loadClientOrganizations(tenant, project);
        Organization rootOrganization = loadRootOrganization(tenant, project);
        // 获取用户组织人员列表
        Set<OrganizationEmployee> orgEmps = loadUserOrganizationEmployees(clientOrgs, user);
        // 加载用户组织信息
        Set<Organization> organizations = loadUserOrganizations(orgEmps);
        Set<TenantEmployee> employees = loadTenantEmployees(orgEmps);
        // 加载用户角色信息
        Set<Role> roles = loadUserRoles(tenant, project, orgEmps);

        // 加载资源信息
        Set<Authority> authorities = loadUserAuthorities(clientId, project, roles);
        if (CollectionUtils.isEmpty(authorities)) {
            log.warn("用户[{}-{}]资源权限为空！！！", clientId, username);
        }

        // 创建用户凭证信息
        UserPrincipal userPrincipal = getUserPrincipal(tenant, project, user, roles, organizations);

        return new UserClientBean()
                .setClientId(clientId)
                .setUser(user)
                .setUserPrincipal(userPrincipal)
                .setTenant(tenant)
                .setProject(project)
                .setOrganizations(new HashSet<>(organizations))
                .setRootOrganization(rootOrganization)
                .setEmployees(employees)
                .setRoles(roles)
                .setAuthorities(authorities);
    }

    public UserPrincipal loadUserPrincipal(String username, UUID clientId) {
        // 获取客户信息
        Tenant tenant = null;
        Project project = null;
        if (!UUID.fromString(Constant.SAAS_MANAGER_CENTER_ID).equals(clientId)) {
            Optional<Tenant> optional = tenantRepository.findById(clientId);
            if (!optional.isPresent()) {
                Optional<Project> projectOptional = projectRepository.findById(clientId);
                if (!projectOptional.isPresent()) {
                    throw ExceptionHolder.serviceException(UaaErrorCode.E2001005007, String.valueOf(clientId));
                } else {
                    project = projectOptional.get();
                    tenant = project.getTenant();
                }
            } else {
                tenant = optional.get();
            }
        }

        // 获取用户信息
        User user = loadUserByMobileOrUserName(username);

        // 加载客户端组织信息
        List<Organization> clientOrgs = loadClientOrganizations(tenant, project);
        // 获取用户组织人员列表
        Set<OrganizationEmployee> orgEmps = loadUserOrganizationEmployees(clientOrgs, user);
        // 加载用户组织信息
        Set<Organization> organizations = loadUserOrganizations(orgEmps);
        // 加载用户角色信息
        Set<Role> roles = loadUserRoles(tenant, project, orgEmps);

        return getUserPrincipal(tenant, project, user, roles, organizations);
    }

    public UserPrincipal getUserPrincipal(Tenant tenant, Project project, User user, Set<Role> roles, Set<Organization> organizations) {
        UUID tenantId = tenant == null ? null : tenant.getId();
        String tenantName = tenant == null ? null : tenant.getName();
        UUID projectId = project == null ? null : project.getId();
        String projectName = project == null ? null : project.getName();
        Organization org = organizations.iterator().hasNext() ? organizations.iterator().next() : null;
        UUID orgId = org == null ? null : org.getId();
        String orgName = org == null ? null : org.getName();
        List<String> roleCodes = roles.stream().map(Role::getCode).collect(Collectors.toList());
        boolean isLeader = isDepartmentLeader(roles);
        return userPrincipalConverter.toUserPrincipal(user, tenantId, tenantName, projectId, projectName, roleCodes, orgId, orgName, isLeader);
    }

    /**
     * 加载客户端组织信息
     *
     * @param tenant  租户信息
     * @param project 项目信息
     * @return 组织信息
     */
    public List<Organization> loadClientOrganizations(Tenant tenant, Project project) {
        if (project != null) {
            return organizationRepository.findByProjectAndDisableFalse(project);
        } else if (tenant != null) {
            return organizationRepository.findByTenantAndDisableFalse(tenant);
        } else {
            return organizationRepository.findAllByDisableFalse();
        }
    }

    public Organization loadRootOrganization(Tenant tenant, Project project) {
        if (project != null) {
            return project.getRootOrganization();
        } else if (tenant != null) {
            return tenant.getRootOrganization();
        }
        return organizationRepository.findByCodeAndDisableFalse(Constant.tenantRootOrgCode(Constant.INTERNAL_TENANT_CODE));
    }

    public Set<OrganizationEmployee> loadUserOrganizationEmployees(List<Organization> clientOrgs, User user) {
        List<OrganizationEmployee> orgEmpList = organizationEmployeeRepository.findByOrganizationInAndUser(clientOrgs, user);
        return new HashSet<>(orgEmpList);
    }

    /**
     * 加载用户组织信息
     */
    public Set<Organization> loadUserOrganizations(Set<OrganizationEmployee> orgEmps) {
        Set<Organization> res = new HashSet<>();
        if (!CollectionUtils.isEmpty(orgEmps)) {
            orgEmps.forEach(organizationEmployee -> res.add(organizationEmployee.getOrganization()));
        }
        return res;
    }

    public Set<TenantEmployee> loadTenantEmployees(Set<OrganizationEmployee> orgEmps) {
        Set<TenantEmployee> employees = new HashSet<>();
        if (!CollectionUtils.isEmpty(orgEmps)) {
            orgEmps.forEach(orgEmp -> employees.add(orgEmp.getEmployee()));
        }
        return employees;
    }

    public Set<Role> loadUserRoles(Tenant tenant, Project project, Set<OrganizationEmployee> orgEmps) {
        Set<Role> roles = new HashSet<>();
        if (!CollectionUtils.isEmpty(orgEmps)) {
            List<OrganizationEmployeeRole> employeeRoles = organizationEmployeeRoleRepository.findByOrganizationEmployeeIn(orgEmps);
            if (!CollectionUtils.isEmpty(employeeRoles)) {
                employeeRoles.forEach(r -> roles.add(r.getRole()));
            }
        }
        Predicate<Role> predicate;
        if (project != null) {
            predicate = r -> Objects.equals(ControlRange.PROJECT, r.getControlRange());
        } else if (tenant != null) {
            predicate = r -> Objects.equals(ControlRange.TENANT, r.getControlRange());
        } else {
            predicate = r -> Objects.equals(ControlRange.SYSTEM, r.getControlRange());
        }
        return roles.stream().filter(predicate).collect(Collectors.toSet());
    }

    public Set<Authority> loadUserAuthorities(UUID clientId, Project project, Set<Role> roles) {
        Set<Authority> authorities = new HashSet<>();
        // 系统管理员登入系统，只可看到系统级别资源
        if (UUID.fromString(Constant.SAAS_MANAGER_CENTER_ID).equals(clientId)) {
            List<Authority> authorityList = authorityRepository.findByCodeStartingWith(Constant.SYSTEM_AUTHORITY_CODE_PREFIX);
            authorities.addAll(authorityList);
        } else if (project != null) {
            // 当前用户登陆项目Endpoint
            List<RoleAuthority> ras = roleAuthorityRepository.findByRoleIn(roles);
            if (!CollectionUtils.isEmpty(ras)) {
                ras.forEach(roleAuthority -> authorities.add(roleAuthority.getAuthority()));
            }
        } else {
            List<Authority> authorityList = authorityRepository.findByCodeStartingWith(Constant.TENANT_AUTHORITY_CODE_PREFIX);
            authorities.addAll(authorityList);
        }
        return authorities;
    }

    private boolean isDepartmentLeader(Set<Role> userRoles) {
        return userRoles.stream().anyMatch(role -> {
            if (!Constant.MANAGER_ROLE_CODES.contains(role.getCode())) {
                return role.getInternalRoles().stream().anyMatch(internalRole -> Constant.MANAGER_ROLE_CODES.contains(internalRole.getCode()));
            }
            return true;
        });
    }
}